package com.googlecode.jerato.library;

import java.util.Hashtable;
import java.util.Map;
import java.io.*;
import java.util.zip.*;
import java.net.MalformedURLException;
import java.net.URL;

public class ExternalClassLoader extends ClassLoader
{
	protected Map _classCacheMap = new Hashtable();
	protected String[] _classPathArray;

	public ExternalClassLoader(String[] classPath){
		if(classPath == null){
			throw new IllegalArgumentException();
		}
		_classPathArray = (String[])classPath.clone();
	}

//	protected Class defineClassPublic(String name, byte data[], int offset, int length) 
//			throws ClassNotFoundException{
//		try{
//			return defineClass(name, data, offset, length);
//		}
//		catch(ClassFormatError ce){
//			throw new ClassNotFoundException(ce.getMessage());
//		}
//		catch(LinkageError le){
//			throw new ClassNotFoundException(le.getMessage());
//		}
//	}

//	protected void resolveClassPublic(Class cls) { 
//		resolveClass(cls);
//	}

	public void destroy(){
		_classCacheMap.clear();
	}

//	protected synchronized Object load(final String name)
//		throws ClassNotFoundException, IllegalAccessException, InstantiationException
//	{
//		Class cls = null;
//		cls = loadClass(name, true);
//
//		return cls.newInstance();
//	}

	protected synchronized void appendClass(Class cls){
		if(!_classCacheMap.containsKey(cls.getName()))
		{
			_classCacheMap.put(cls.getName(), cls);
		}
	}

	public synchronized Class loadClass(final String name, boolean flg)
		throws ClassNotFoundException
	{
		Class cachClass = null;
		cachClass = (Class)_classCacheMap.get(name);
		if(cachClass != null){
			return cachClass;
		}

		try
		{
			cachClass = findSystemClass(name);
			if(flg){
				resolveClass(cachClass);
				appendClass(cachClass);
			}
			return cachClass;
		}
		catch(ClassNotFoundException cne)
		{
		}

		boolean loadSuccess = false;
		for(int loop = 0; loop < _classPathArray.length; loop++)
		{
			try
			{
				File file = new File(_classPathArray[loop]);
				if(file.isFile() && isZipFile(_classPathArray[loop]))
				{
					String path = name.replaceAll("\\.", "/") + ".class";
					byte[] data = loadResourceFromZipfile(_classPathArray[loop], path);
					if(data == null)
						continue;
					if(data.length > 0)
					{
						cachClass = defineClass(name, data, 0, data.length);
						loadSuccess = true;
						break;
					}
				}
				else if(file.isDirectory())
				{
					String path = file + File.separator + name.replaceAll("\\.", File.separator) + ".class";
					if(!(new File(path)).isFile())
						continue;
					byte[] data = getResourceBytes(path);
					cachClass = defineClass(name, data, 0, data.length);
					loadSuccess = true;
					break;
				}
			}
			catch(IOException ioe)
			{
			}
		}

		if(!loadSuccess){
			throw new ClassNotFoundException("Class Not Fount:" + name);
		}

		if(flg){
			resolveClass(cachClass);
			appendClass(cachClass);
		}
		return cachClass;
	}

	protected boolean isZipFile(String file) {

		try
		{
			ZipFile zipFile = null;
			try{
				zipFile = new ZipFile(file);
			}
			finally{
				if (zipFile != null)
				{
					zipFile.close();
				}
			}
			return true;
		}
		catch (IOException ioe)
		{
			return false;
		}
	}

	protected byte[] getResourceBytes(String name)
		throws IOException
	{
		if(new File(name).exists()){
			FileInputStream input = null;
			try{
				try{
					input = new FileInputStream(name);
					byte[] buffer = new byte[input.available()];
					input.read(buffer, 0, buffer.length);
					return buffer;
				}
				finally{
					if(input != null){
						input.close();
					}
				}
			}
			catch(IOException ioe){
			}
		}
		return null;
	}

	protected byte[] loadResourceFromZipfile(String file, String name)
		throws IOException
	{
		ZipFile zipfile = null;
		try {
			zipfile = new ZipFile(file);
			ZipEntry entry = zipfile.getEntry(name);
			if(entry == null){
				return null;
			}

			int size = (int)entry.getSize();
			byte[] buffer = new byte[size];
			InputStream stream = null;
			int totalRead = 0;
			try{
				stream = zipfile.getInputStream(entry);
				while(totalRead < size){
					int readSize = stream.read(buffer, totalRead, buffer.length - totalRead);
					totalRead += readSize;
				}
				return buffer;
			}
			finally{
				if(stream != null){
					stream.close();
				}
			}
		} finally {
			if ( zipfile != null ) {
				try{
					zipfile.close();
				}
				catch ( IOException ioe ){
				}
			}
		}
	}

	public InputStream getResourceAsStream(String name)
	{
		for(int loop = 0; loop < _classPathArray.length; loop++)
		{
			File file = new File(_classPathArray[loop]);
			if(file.isDirectory())
			{
				try
				{
					String fullPath = _classPathArray[loop] + File.separator + name;
					if((new File(fullPath)).isFile())
					{
						byte[] data = getResourceBytes(fullPath);
						if(data != null)
						{
							return new ByteArrayInputStream(data);
						}
					}
					else
					{
						String path = _classPathArray[loop] + File.separator + name;
						byte[] data = getResourceBytes(path);
						if(data != null)
						{
							return new ByteArrayInputStream(data);
						}
					}
				}
				catch(IOException ioe)
				{
				}
			}
		}
		return null;
	}
	public URL getResource(String name)
	{
		URL url = super.getResource(name);
		if(url != null)
			return url;
		if(name == null)
			return null;
		for(int loop = 0; loop < _classPathArray.length; loop++)
		{
			String path = _classPathArray[loop];
			if(!path.endsWith(File.separator)){
				path += File.separator;
			}
			path += name;
			path = new File(path).getAbsolutePath();
			if((new File(path)).exists())
			{
				try
				{
					return new URL("file:" + path);
				}
				catch(MalformedURLException e){}
			}
		}
		return null;
	}
	

//	public Object loadObject(InputStream in)
//				throws IOException, ClassNotFoundException{
//		ObjectInputStream inObj = new ObjectInputStream(in);
//		return inObj.readObject();
//	}
}
